/***************************************************************** BioZen Copyright (C) 2011 The National Center for Telehealth and Technology Eclipse Public License 1.0 (EPL-1.0) This library is free software; you can redistribute it and/or modify it under the terms of the Eclipse Public License as published by the Free Software Foundation, version 1.0 of the License. The Eclipse Public License is a reciprocal license, under Section 3. REQUIREMENTS iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. Post your updates and modifications to our GitHub or email to t2@tee2.org. This library is distributed WITHOUT ANY WARRANTY; without the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Eclipse Public License 1.0 (EPL-1.0) for more details. You should have received a copy of the Eclipse Public License along with this library; if not, visit http://www.opensource.org/licenses/EPL-1.0 *****************************************************************/ package com.t2.compassionMeditation; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.Locale; import java.util.Timer; import java.util.TimerTask; import java.util.Vector; import org.achartengine.ChartFactory; import org.achartengine.GraphicalView; import org.achartengine.chart.PointStyle; import org.achartengine.model.XYMultipleSeriesDataset; import org.achartengine.model.XYSeries; import org.achartengine.renderer.XYMultipleSeriesRenderer; import org.achartengine.renderer.XYSeriesRenderer; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.t2health.lib1.BioParameter; import org.t2health.lib1.BioSensor; import spine.SPINEFactory; import spine.SPINEFunctionConstants; import spine.SPINEListener; import spine.SPINEManager; import spine.SPINESensorConstants; import spine.datamodel.Address; import spine.datamodel.Data; import spine.datamodel.Feature; import spine.datamodel.FeatureData; import spine.datamodel.HeartBeatData; import spine.datamodel.MindsetData; import spine.datamodel.Node; import spine.datamodel.ServiceMessage; import spine.datamodel.ShimmerData; import spine.datamodel.functions.ShimmerNonSpineSetupSensor; import android.app.AlarmManager; import android.app.AlertDialog; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.AssetManager; import android.content.res.Resources; import android.graphics.Color; import android.graphics.PorterDuff; import android.os.Bundle; import android.os.Environment; import android.os.IBinder; import android.os.SystemClock; import android.preference.PreferenceManager; import android.telephony.TelephonyManager; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.style.ForegroundColorSpan; import android.util.Log; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.view.Window; import android.view.WindowManager; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import bz.org.t2health.lib.activity.BaseActivity; import com.t2.Constants; import com.t2.R; import com.t2.SpineReceiver; import com.t2.SpineReceiver.BioFeedbackStatus; import com.t2.SpineReceiver.OnBioFeedbackMessageRecievedListener; import com.t2.antlib.ANTPlusService; import com.t2.antlib.AntPlusManager; import com.t2.biofeedback.device.shimmer.ShimmerDevice; import com.t2.compassionUtils.MathExtra; import com.t2.compassionUtils.Util; import com.t2.dataouthandler.DataOutHandler; import com.t2.dataouthandler.DataOutHandlerException; import com.t2.dataouthandler.DataOutHandlerTags; import com.t2.dataouthandler.DataOutPacket; import com.t2.t2sensorlib.BigBrotherService; public class Graphs1Activity extends BaseActivity implements OnBioFeedbackMessageRecievedListener, SPINEListener, AntPlusManager.Callbacks { private static final String TAG = "BFDemo"; private static final String KEY_NAME = "results_visible_ids_16"; private static final int BLUETOOTH_SETTINGS_ID = 987; private static final int HEARTRATE_SHIMMER = 1; private static final int HEARTRATE_ZEPHYR = 2; private static final int HEARTRATE_ANT = 3; private String mAppId = "bioZenGraphs"; private boolean mLogCatEnabled = true; private boolean mLoggingEnabled = true; private int mPrevSigQuality = 0; private boolean mInternalSensorMonitoring = false; /** * Intent to start Big Brother service */ private PendingIntent mBigBrotherService; private int mPollingPeriod = 30; // seconds private int mSecondsWithoutActivityThreshold = 5; // seconds private double mAccelerationThreshold = 12.0; // m/s^2 /** * Application version info determined by the package manager */ private String mApplicationVersion = ""; /** * The Spine manager contains the bulk of the Spine server. */ private static SPINEManager mManager; /** * This is a broadcast receiver. Note that this is used ONLY for command/status messages from the AndroidBTService * All data from the service goes through the mail SPINE mechanism (received(Data data)). */ private SpineReceiver mCommandReceiver; /** * Static instance of this activity */ private static Graphs1Activity mInstance; /** * Timer for updating the UI */ private static Timer mDataUpdateTimer; /** * Timer for Resp Rate Average */ private static Timer mRespRateAverageTimer; protected SharedPreferences sharedPref; private boolean mPaused = false; private Boolean mBluetoothEnabled = false; // UI Elements private Button mAddMeasureButton; private Button mPauseButton; private TextView mTextInfoView; private TextView mMeasuresDisplayText; private MindsetData currentMindsetData; private GraphicalView mDeviceChartView; private int mConfiguredGSRRange = ShimmerDevice.GSR_RANGE_HW_RES_3M3; /** * List of all BioParameters used in this activity */ private ArrayList<GraphBioParameter> mBioParameters = new ArrayList<GraphBioParameter>(); /** * List of all currently PAIRED BioSensors */ private ArrayList<BioSensor> mBioSensors = new ArrayList<BioSensor>(); /** * Class to help in saving received data to file */ private DataOutHandler mDataOutHandler; /** * Class to help in processing biometeric data */ private BioDataProcessor mBioDataProcessor = new BioDataProcessor(this); // Charting stuff private final static int SPINE_CHART_SIZE = 20; private int mSpineChartX = 0; private Node mShimmerNode = null; /** * Node object for shimmer device as returned by spine */ public Node mSpineNode = null; private int numTicsWithoutData = 0; private static Object mKeysLock = new Object(); private static Object mRespRateAverageLock = new Object(); // We'll use these to get easy access to parameters in the mBioParameters array private int eegPos; private int gsrPos; private int emgPos; private int ecgPos; private int heartRatePos; private int respRatePos; private int skinTempPos; private int eHealthAirFlowPos; private int eHealthTempPos; private int eHealthSpO2Pos; private int eHealthHeartRatePos; private int eHealthGSRPos; boolean mIsActive = false; int mDisplaySampleRate; // int mHeartRateSource = HEARTRATE_SHIMMER; int mHeartRateSource = HEARTRATE_ZEPHYR; long mLastRespRateTime; int mRespRateTotal; int mRespRateIndex; private boolean mDatabaseEnabled; private boolean mAntHrmEnabled; /** * Static names dealing with the external database */ public static final String dDatabaseName = ""; public static final String dDesignDocName = "bigbrother-local"; public static final String dDesignDocId = "_design/" + dDesignDocName; public static final String byDateViewName = "byDate"; /** Class to manage all the ANT messaging and setup */ private AntPlusManager mAntManager; private boolean mAntServiceBound; /** Shared preferences data filename. */ public static final String PREFS_NAME = "ANTDemo1Prefs"; /** Pair to any device. */ static final short ANT_WILDCARD = 0; /** The default proximity search bin. */ private static final byte ANT_DEFAULT_BIN = 7; /** The default event buffering buffer threshold. */ private static final short ANT_DEFAULT_BUFFER_THRESHOLD = 0; /** * Right now we're using only one shimmer node for all shimmer devices * (since we address they by BT address) * @return singleton for the shimmer node */ private Node getShimmerNode() { if (mShimmerNode == null) { mShimmerNode = new Node(new Address("" + Constants.RESERVED_ADDRESS_SHIMMER)); mManager.getActiveNodes().add(mShimmerNode); } return mShimmerNode; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.i(TAG, this.getClass().getSimpleName() + ".onCreate()"); // We don't want the screen to timeout in this activity getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); this.requestWindowFeature(Window.FEATURE_NO_TITLE); // This needs to happen BEFORE setContentView setContentView(R.layout.graphs_activity_layout); mInstance = this; sharedPref = PreferenceManager.getDefaultSharedPreferences(getBaseContext()); setRequestedOrientation (ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); mLoggingEnabled = SharedPref.getBoolean(this, "enable_logging", true); mDatabaseEnabled = SharedPref.getBoolean(this, "database_enabled", false); mAntHrmEnabled = SharedPref.getBoolean(this, "enable_ant_hrm", false); mInternalSensorMonitoring = SharedPref.getBoolean(this, "inernal_sensor_monitoring_enabled", false); if (mAntHrmEnabled) { mHeartRateSource = HEARTRATE_ANT; } else { mHeartRateSource = HEARTRATE_ZEPHYR; } // The session start time will be used as session id // Note this also sets session start time // **** This session ID will be prepended to all JSON data stored // in the external database until it's changed (by the start // of a new session. Calendar cal = Calendar.getInstance(); SharedPref.setBioSessionId(sharedPref, cal.getTimeInMillis()); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.US); String sessionDate = sdf.format(new Date()); String userId = SharedPref.getString(this, "SelectedUser", ""); long sessionId = SharedPref.getLong(this, "bio_session_start_time", 0); mDataOutHandler = new DataOutHandler(this, userId,sessionDate, mAppId, DataOutHandler.DATA_TYPE_EXTERNAL_SENSOR, sessionId ); if (mDatabaseEnabled) { TelephonyManager telephonyManager = (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE); String myNumber = telephonyManager.getLine1Number(); String remoteDatabaseUri = SharedPref.getString(this, "database_sync_name", getString(R.string.database_uri)); // remoteDatabaseUri += myNumber; Log.d(TAG, "Initializing database at " + remoteDatabaseUri); // TODO: remove try { mDataOutHandler.initializeDatabase(dDatabaseName, dDesignDocName, dDesignDocId, byDateViewName, remoteDatabaseUri); } catch (DataOutHandlerException e) { Log.e(TAG, e.toString()); e.printStackTrace(); } mDataOutHandler.setRequiresAuthentication(false); } mBioDataProcessor.initialize(mDataOutHandler); if (mLoggingEnabled) { mDataOutHandler.enableLogging(this); } if (mLogCatEnabled) { mDataOutHandler.enableLogCat(); } // Log the version try { PackageManager packageManager = getPackageManager(); PackageInfo info = packageManager.getPackageInfo(getPackageName(), 0); mApplicationVersion = info.versionName; String versionString = mAppId + " application version: " + mApplicationVersion; DataOutPacket packet = new DataOutPacket(); packet.add(DataOutHandlerTags.version, versionString); try { mDataOutHandler.handleDataOut(packet); } catch (DataOutHandlerException e) { Log.e(TAG, e.toString()); e.printStackTrace(); } } catch (NameNotFoundException e) { Log.e(TAG, e.toString()); } // Set up UI elements Resources resources = this.getResources(); AssetManager assetManager = resources.getAssets(); mPauseButton = (Button) findViewById(R.id.buttonPause); mAddMeasureButton = (Button) findViewById(R.id.buttonAddMeasure); mTextInfoView = (TextView) findViewById(R.id.textViewInfo); mMeasuresDisplayText = (TextView) findViewById(R.id.measuresDisplayText); // Don't actually show skin conductance meter unless we get samples ImageView image = (ImageView) findViewById(R.id.imageView1); image.setImageResource(R.drawable.signal_bars0); // Check to see of there a device configured for EEG, if so then show the skin conductance meter String tmp = SharedPref.getString(this, "EEG" ,null); if (tmp != null) { image.setVisibility(View.VISIBLE); } else { image.setVisibility(View.INVISIBLE); } // Initialize SPINE by passing the fileName with the configuration properties try { mManager = SPINEFactory.createSPINEManager("SPINETestApp.properties", resources); } catch (InstantiationException e) { Log.e(TAG, "Exception creating SPINE manager: " + e.toString()); e.printStackTrace(); } try { currentMindsetData = new MindsetData(this); } catch (Exception e1) { Log.e(TAG, "Exception creating MindsetData: " + e1.toString()); e1.printStackTrace(); } // Establish nodes for BSPAN // Create a broadcast receiver. Note that this is used ONLY for command messages from the service // All data from the service goes through the mail SPINE mechanism (received(Data data)). // See public void received(Data data) this.mCommandReceiver = new SpineReceiver(this); int itemId = 0; eegPos = itemId; // eeg always comes first mBioParameters.clear(); // First create GraphBioParameters for each of the EEG static params (ie mindset) for (itemId = 0; itemId < MindsetData.NUM_BANDS + 2; itemId++) { // 2 extra, for attention and meditation GraphBioParameter param = new GraphBioParameter(itemId, MindsetData.spectralNames[itemId], "", true); param.isShimmer = false; mBioParameters.add(param); } // Now create all of the potential dynamic GBraphBioParameters (GSR, EMG, ECG, EEG, HR, Skin Temp, Resp Rate // String[] paramNamesStringArray = getResources().getStringArray(R.array.parameter_names); String[] paramNamesStringArray = getResources().getStringArray(R.array.parameter_names_less_eeg); for (String paramName: paramNamesStringArray) { if (paramName.equalsIgnoreCase("not assigned")) continue; GraphBioParameter param = new GraphBioParameter(itemId, paramName, "", true); if (paramName.equalsIgnoreCase("gsr")) { gsrPos = itemId; param.isShimmer = true; param.shimmerSensorConstant = SPINESensorConstants.SHIMMER_GSR_SENSOR; param.shimmerNode = getShimmerNode(); } if (paramName.equalsIgnoreCase("emg")) { emgPos = itemId; param.isShimmer = true; param.shimmerSensorConstant = SPINESensorConstants.SHIMMER_EMG_SENSOR; param.shimmerNode = getShimmerNode(); } if (paramName.equalsIgnoreCase("ecg")) { ecgPos = itemId; param.isShimmer = true; param.shimmerSensorConstant = SPINESensorConstants.SHIMMER_ECG_SENSOR; param.shimmerNode = getShimmerNode(); } if (paramName.equalsIgnoreCase("heart rate")) { heartRatePos = itemId; param.isShimmer = false; } if (paramName.equalsIgnoreCase("resp rate")) { respRatePos = itemId; param.isShimmer = false; } if (paramName.equalsIgnoreCase("skin temp")) { skinTempPos = itemId; param.isShimmer = false; } if (paramName.equalsIgnoreCase("EHealth Airflow")) { eHealthAirFlowPos = itemId; param.isShimmer = false; } if (paramName.equalsIgnoreCase("EHealth Temp")) { eHealthTempPos = itemId; param.isShimmer = false; } if (paramName.equalsIgnoreCase("EHealth SpO2")) { eHealthSpO2Pos = itemId; param.isShimmer = false; } if (paramName.equalsIgnoreCase("EHealth Heartrate")) { eHealthHeartRatePos = itemId; param.isShimmer = false; } if (paramName.equalsIgnoreCase("EHealth GSR")) { eHealthGSRPos = itemId; param.isShimmer = false; } itemId++; mBioParameters.add(param); } // Since These are static nodes (Non-spine) we have to manually put them in the active node list Node mindsetNode = null; mindsetNode = new Node(new Address("" + Constants.RESERVED_ADDRESS_MINDSET)); // Note that the sensor id 0xfff1 (-15) is a reserved id for this particular sensor mManager.getActiveNodes().add(mindsetNode); Node zepherNode = null; zepherNode = new Node(new Address("" + Constants.RESERVED_ADDRESS_ZEPHYR)); mManager.getActiveNodes().add(zepherNode); // The arduino node is programmed to look like a static Spine node // Note that currently we don't have to turn it on or off - it's always streaming // Since Spine (in this case) is a static node we have to manually put it in the active node list // Since the final int RESERVED_ADDRESS_ARDUINO_SPINE = 1; // 0x0001 mSpineNode = new Node(new Address("" + RESERVED_ADDRESS_ARDUINO_SPINE)); mManager.getActiveNodes().add(mSpineNode); final String sessionName; // Check to see if we were requested to play back a previous session try { Bundle bundle = getIntent().getExtras(); if (bundle != null) { sessionName = bundle.getString(BioZenConstants.EXTRA_SESSION_NAME); AlertDialog.Builder alert = new AlertDialog.Builder(this); alert.setTitle("Replay Session " + sessionName + "?"); alert.setMessage("Make sure to turn off all Bluetooth Sensors!"); alert.setPositiveButton(R.string.alert_dialog_ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { try { mDataOutHandler.logNote("Replaying data from session " + sessionName); } catch (DataOutHandlerException e) { Log.e(TAG, e.toString()); e.printStackTrace(); } replaySessionData(sessionName); AlertDialog.Builder alert1 = new AlertDialog.Builder(mInstance); alert1.setTitle("INFO"); alert1.setMessage("Replay of session complete!"); alert1.show(); } }); alert.show(); } } catch (Exception e) { Log.e(TAG, e.toString()); e.printStackTrace(); } if (mInternalSensorMonitoring) { // IntentSender Launches our service scheduled with with the alarm manager mBigBrotherService = PendingIntent.getService(Graphs1Activity.this, 0, new Intent(Graphs1Activity.this, BigBrotherService.class), 0); long firstTime = SystemClock.elapsedRealtime(); // Schedule the alarm! AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE); am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, firstTime, mPollingPeriod * 1000, mBigBrotherService); // Tell the user about what we did. Toast.makeText(Graphs1Activity.this, R.string.service_scheduled, Toast.LENGTH_LONG).show(); } //testFIRFilter(); // testHR(); } // End onCreate( boolean replaySessionData(String sessionName) { BufferedReader logReader = null; // Open a file for saving data try { File root = Environment.getExternalStorageDirectory(); if (root.canWrite()){ File gpxfile = new File(root, sessionName); FileReader gpxreader = new FileReader(gpxfile); // open for append logReader = new BufferedReader(gpxreader); String lineToParse; while ((lineToParse = logReader.readLine()) != null) { Log.i("SensorData",lineToParse); if (lineToParse.contains("ECG,")) { try { String[] tokens = lineToParse.split(","); if (tokens.length == 7) { ShimmerData shimmerData = new ShimmerData(SPINEFunctionConstants.SHIMMER, SPINESensorConstants.SHIMMER_ECG_SENSOR, (byte) 0); shimmerData.setFunctionCode(SPINEFunctionConstants.SHIMMER); shimmerData.sensorCode = SPINESensorConstants.SHIMMER_ECG_SENSOR; shimmerData.ecgLaLL = 2000 + (int) Float.parseFloat(tokens[5].trim()); shimmerData.ecgRaLL = 2000; shimmerData.timestamp = Integer.parseInt(tokens[1].trim()); this.received(shimmerData); } if (tokens.length == 6) { ShimmerData shimmerData = new ShimmerData(SPINEFunctionConstants.SHIMMER, SPINESensorConstants.SHIMMER_ECG_SENSOR, (byte) 0); shimmerData.setFunctionCode(SPINEFunctionConstants.SHIMMER); shimmerData.sensorCode = SPINESensorConstants.SHIMMER_ECG_SENSOR; shimmerData.ecgLaLL = 2000 + Integer.parseInt(tokens[4].trim()); shimmerData.ecgRaLL = 2000; shimmerData.timestamp = Integer.parseInt(tokens[2].trim()); this.received(shimmerData); } if (tokens.length == 5) { ShimmerData shimmerData = new ShimmerData(SPINEFunctionConstants.SHIMMER, SPINESensorConstants.SHIMMER_ECG_SENSOR, (byte) 0); shimmerData.setFunctionCode(SPINEFunctionConstants.SHIMMER); shimmerData.sensorCode = SPINESensorConstants.SHIMMER_ECG_SENSOR; shimmerData.ecgLaLL = 2000 + Integer.parseInt(tokens[3].trim()); shimmerData.ecgRaLL = 2000; shimmerData.timestamp = Integer.parseInt(tokens[2].trim()); this.received(shimmerData); } } catch (Exception e) { } } } } else { Log.e(TAG, "Could not open file " ); AlertDialog.Builder alert = new AlertDialog.Builder(this); alert.setTitle("ERROR"); alert.setMessage("Cannot open to file"); alert.show(); } } catch (IOException e) { Log.e(TAG, "Could not open file " + e.getMessage()); AlertDialog.Builder alert = new AlertDialog.Builder(this); alert.setTitle("ERROR"); alert.setMessage("Cannot write to file"); alert.show(); } try { logReader.close(); } catch (IOException e) { Log.e(TAG, "Could not close file " + e.getMessage()); } return true; } private void testHR() { byte[] input = {-22,-29,-33,-93,-48,27,-3,24,-20,-44,22,-40,-70,-82,33,-64,-23,4,-20,28,-78,3,7,23,92,43,14,19,-27,111,93,-38,93,-31,-29,-40,2,59,70,-76,-14,15,-44,-66,-17,-16,-19,-18,-10,-16,53,98,-23,-23,-15,-14,-8,-1,3,-20,-10,-26,-5,-23,-9,-6,18,18,17,16,33,56,46,10,7,9,20,0,12,10,8,8,-5,14,-5,6,-12,1,-9,22,-12,-7,-29,-18,59,47,-25,-31,-33,-39,-38,-27,-15,-8,-45,-42,-26,-15,-32,-13,8,4,21,31,17,22,8,17,5,16,2,0,3,34,1,-1,7,9,6,5,11,-9,20,7,-14,-17,29,99,15,-23,-32,-11,-27,-30,-24,-35,-18,-40,-22,-30,-34,-21,1,27,13,32,29,14,29,29,14,7,14,11,17,-5,9,-4,4,14,-1,8,11,6,12,8,-1,-40,50,98,-29,-19,-33,-22,-20,-30,-36,-5,-28,-28,-49,-45,-38,5,21,12,27,13,14,14,8,22,7,28,6,18,8,8,12,1,-7,-13,-22,19,-11,19,-7,4,-2,43,102,-17,-26,-33,-44,-15,-25,-36,-29,-19,-35,-24,-33,-10,-8,18,44,17,21,12,13,38,1,14,10,6,13,2,-2,12}; long timeStamp = 0; for (int i = 0; i < input.length; i++) { ShimmerData shimmerData = new ShimmerData(SPINEFunctionConstants.SHIMMER, SPINESensorConstants.SHIMMER_ECG_SENSOR, (byte) 0); shimmerData.setFunctionCode(SPINEFunctionConstants.SHIMMER); shimmerData.sensorCode = SPINESensorConstants.SHIMMER_ECG_SENSOR; shimmerData.ecgLaLL = 2000 + input[i]; shimmerData.ecgRaLL = 2000; shimmerData.timestamp = (int) timeStamp; timeStamp += 512; this.received(shimmerData); } } @Override protected void onDestroy() { super.onDestroy(); if (mInternalSensorMonitoring) { // And cancel the alarm. AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE); am.cancel(mBigBrotherService); Intent intent = new Intent(); intent.setAction(BigBrotherConstants.ACTION_COMMAND_BROADCAST); intent.putExtra("message", BigBrotherConstants.SERVICE_OFF); sendBroadcast(intent); // Tell the user about what we did. Toast.makeText(Graphs1Activity.this, R.string.service_unscheduled, Toast.LENGTH_LONG).show(); } mDataOutHandler.close(); if (mDataUpdateTimer != null) { mDataUpdateTimer.cancel(); mDataUpdateTimer.purge(); } if (mRespRateAverageTimer != null) { mRespRateAverageTimer.cancel(); mRespRateAverageTimer.purge(); } Log.i(TAG, this.getClass().getSimpleName() + ".onDestroy()"); // Send stop command to every shimmer device // You might think that it would be better to iterate through the mBioSensors table // instead of the mBioParameters table but it's actually easier this way for (GraphBioParameter param : mBioParameters) { if (param.isShimmer && param.shimmerNode != null) { ShimmerNonSpineSetupSensor setup = new ShimmerNonSpineSetupSensor(); setup.setSensor(param.shimmerSensorConstant); String deviceAddress = SharedPref.getDeviceForParam(this, param.title1); if (deviceAddress != null) { setup.setBtAddress(Util.AsciiBTAddressToBytes(deviceAddress)); setup.setCommand(ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_STOPPED); Log.d(TAG, String.format("Setting up Shimmer sensor: %s (%s) (%d) SHIMMER_COMMAND_STOPPED", param.shimmerNode.getPhysicalID(), deviceAddress, param.shimmerSensorConstant)); mManager.setup(param.shimmerNode, setup); } } } this.unregisterReceiver(this.mCommandReceiver); } @Override protected void onStart() { super.onStart(); mIsActive = true; // we need to register a SPINEListener implementation to the SPINE manager instance // to receive sensor node data from the Spine server // (I register myself since I'm a SPINEListener implementation!) mManager.addListener(this); Log.i(TAG, this.getClass().getSimpleName() + ".onStart()"); // Set up filter intents so we can receive broadcasts IntentFilter filter = new IntentFilter(); filter.addAction("com.t2.biofeedback.service.status.BROADCAST"); this.registerReceiver(this.mCommandReceiver,filter); int displaySampleTime = 1000; String s = SharedPref.getString(this, "display_sample_rate" ,"1"); mDisplaySampleRate = Integer.parseInt(s); switch (mDisplaySampleRate) { default: case 1: displaySampleTime = 1000; Log.d(TAG, "Setting display sample rate to " + mDisplaySampleRate + " Hz"); break; case 10: displaySampleTime = 100; Log.d(TAG, "Setting display sample rate to " + mDisplaySampleRate + " Hz"); break; case 100: displaySampleTime = 10; Log.d(TAG, "Setting display sample rate to " + mDisplaySampleRate + " Hz"); break; case 9999: displaySampleTime = 9999; Log.d(TAG, "Setting display sample rate to match sensor sample rate"); break; } if (mDisplaySampleRate != 9999) { // Set up a timer to do graphical updates mDataUpdateTimer = new Timer(); mDataUpdateTimer.schedule(new TimerTask() { @Override public void run() { TimerMethod(); } }, 0, displaySampleTime); } // Set up a timer for GSR average reporting (10 seconds mRespRateAverageTimer = new Timer(); mRespRateAverageTimer.schedule(new TimerTask() { @Override public void run() { respRateAverageMethod(); } }, 0, 10000); if (mAntHrmEnabled) { mAntServiceBound = bindService(new Intent(this, ANTPlusService.class), mConnection, BIND_AUTO_CREATE); } } @Override protected void onStop() { super.onStop(); if (mDataUpdateTimer != null) mDataUpdateTimer.cancel(); if(mAntManager != null) { saveAntState(); mAntManager.setCallbacks(null); if (mAntManager.isChannelOpen(AntPlusManager.HRM_CHANNEL)) { Log.d(TAG, "onClick (HRM): Close channel"); mAntManager.closeChannel(AntPlusManager.HRM_CHANNEL); } } if(mAntServiceBound) { unbindService(mConnection); } Log.i(TAG, this.getClass().getSimpleName() + ".onStop()"); } @Override protected void onPause() { super.onPause(); mIsActive = false; // ******************* // Make sure to to this or else you will get more and more notifications from Spine as you // go into and out of activities! // Also make sure to do this in on pause (as opposed to onStop or ondestroy. // This will prevent you from receiving messages possibly requested by another activity mManager.removeListener(this); Log.i(TAG, this.getClass().getSimpleName() + ".onPause()"); } @Override protected void onResume() { super.onResume(); Log.i(TAG, this.getClass().getSimpleName() + ".onResume()"); mLastRespRateTime = System.currentTimeMillis(); int mRespRateTotal = 0; int mRespRateIndex = 0; // Set up Device data chart generateChart(); mManager.discoveryWsn(); // discoveryCompleted() is called after this is done } @Override public void newNodeDiscovered(Node newNode) { Log.d(TAG, this.getClass().getSimpleName() + ".newNodeDiscovered()" + newNode.toString()); } @Override public void received(ServiceMessage msg) { } /** * This is where we receive sensor data that comes through the actual * Spine channel. * @param data Generic Spine data packet. Should be cast to specifid data type indicated by data.getFunctionCode() * * @see spine.SPINEListener#received(spine.datamodel.Data) */ @Override public void received(Data data) { //Log.d(TAG, this.getClass().getSimpleName() + ".received()"); if (data != null) { switch (data.getFunctionCode()) { // E-Health board case SPINEFunctionConstants.FEATURE: { FeatureData featureData = (FeatureData) data; Feature[] feats = featureData.getFeatures(); if (feats.length < 2) { break; } Feature firsFeat = feats[0]; Feature Feat2 = feats[1]; int airFlow = firsFeat.getCh1Value(); int scaledTemp = firsFeat.getCh2Value(); float temp = (float)scaledTemp/(65535F/9F) + 29F; int BPM = firsFeat.getCh3Value(); int SPO2 = firsFeat.getCh4Value(); int scaledConductance = Feat2.getCh1Value(); float conductance = (float) scaledConductance / (65535F/4F); Log.d(TAG, "E-health Values = " + airFlow + ", " + temp + ", " + BPM + ", " + SPO2 + ", " + conductance + ", " ); synchronized(mKeysLock) { mBioParameters.get(eHealthAirFlowPos).rawValue = airFlow; mBioParameters.get(eHealthAirFlowPos).scaledValue = (int) map(airFlow,0,360,0,100); mBioParameters.get(eHealthTempPos).rawValue = (int) temp; mBioParameters.get(eHealthTempPos).scaledValue = (int) map(temp,29,40,0,100); mBioParameters.get(eHealthHeartRatePos).rawValue = BPM; mBioParameters.get(eHealthHeartRatePos).scaledValue = (int) map(BPM,30,220,0,100); mBioParameters.get(eHealthSpO2Pos).rawValue = SPO2; mBioParameters.get(eHealthSpO2Pos).scaledValue = SPO2; mBioParameters.get(eHealthGSRPos).rawValue = (int) map(scaledConductance,0,65535,0,100); mBioParameters.get(eHealthGSRPos).scaledValue = (int) map(scaledConductance,0,65535,0,100);; DataOutPacket packet = new DataOutPacket(); packet.add(DataOutHandlerTags.RAW_HEARTRATE, BPM); packet.add(DataOutHandlerTags.RAW_GSR, conductance); packet.add(DataOutHandlerTags.RAW_SKINTEMP, temp); packet.add(DataOutHandlerTags.SPO2, SPO2); packet.add(DataOutHandlerTags.AIRFLOW, airFlow); try { mDataOutHandler.handleDataOut(packet); } catch (DataOutHandlerException e) { Log.e(TAG, e.toString()); // e.printStackTrace(); } } break; } case SPINEFunctionConstants.HEARTBEAT: { synchronized(mKeysLock) { HeartBeatData thisData = (HeartBeatData) data; int scaled = (thisData.getBPM() )/2 ; if (mHeartRateSource == HEARTRATE_ANT) { mBioParameters.get(heartRatePos).rawValue = thisData.getBPM(); mBioParameters.get(heartRatePos).scaledValue = scaled; } // Send data to output DataOutPacket packet = new DataOutPacket(); packet.add(DataOutHandlerTags.RAW_HEARTRATE, thisData.getBPM()); try { mDataOutHandler.handleDataOut(packet); } catch (DataOutHandlerException e) { Log.e(TAG, e.toString()); // e.printStackTrace(); } // See if we are configured to update display every time we get sensor data if (mDisplaySampleRate == 9999 && mIsActive) { this.runOnUiThread(Timer_Tick); } } break; } case SPINEFunctionConstants.SHIMMER: { Node node = data.getNode(); numTicsWithoutData = 0; Node source = data.getNode(); ShimmerData shimmerData = (ShimmerData) data; synchronized(mKeysLock) { switch (shimmerData.sensorCode) { case SPINESensorConstants.SHIMMER_GSR_SENSOR: mBioDataProcessor.processShimmerGSRData(shimmerData, mConfiguredGSRRange); mBioParameters.get(gsrPos).rawValue = (int) (mBioDataProcessor.mGsrConductance * 1000); // scale by 1000 to fit a float into an int double scaled = mBioDataProcessor.mGsrConductance * 10; mBioParameters.get(gsrPos).scaledValue = (int) scaled; // See if we are configured to update display every time we get sensor data if (mDisplaySampleRate == 9999 && mIsActive) { this.runOnUiThread(Timer_Tick); } break; case SPINESensorConstants.SHIMMER_EMG_SENSOR: mBioDataProcessor.processShimmerEMGData(shimmerData); scaled = MathExtra.scaleData((float)shimmerData.emg, 4000F, 0F, 100); mBioParameters.get(emgPos).rawValue = (int) scaled; mBioParameters.get(emgPos).scaledValue = (int) scaled; // See if we are configured to update display every time we get sensor data if (mDisplaySampleRate == 9999) { this.runOnUiThread(Timer_Tick); } break; case SPINESensorConstants.SHIMMER_ECG_SENSOR: // If we're receiving packets from shimmer egg then swith the heartrate to shimmer // Otherwise we'll leave it at the default which is zephyr mHeartRateSource = HEARTRATE_SHIMMER; mBioDataProcessor.processShimmerECGData(shimmerData); scaled = (mBioDataProcessor.mRawEcg + 50 )/2 ; mBioParameters.get(ecgPos).rawValue = (int) scaled; mBioParameters.get(ecgPos).scaledValue = (int) scaled; if (mHeartRateSource == HEARTRATE_SHIMMER) { mBioParameters.get(heartRatePos).rawValue = mBioDataProcessor.mShimmerHeartRate; mBioParameters.get(heartRatePos).scaledValue = mBioDataProcessor.mShimmerHeartRate; } // See if we are configured to update display every time we get sensor data if (mDisplaySampleRate == 9999) { this.runOnUiThread(Timer_Tick); } break; } } break; } case SPINEFunctionConstants.ZEPHYR: { numTicsWithoutData = 0; mBioDataProcessor.processZephyrData(data); synchronized(mKeysLock) { if (mHeartRateSource == HEARTRATE_ZEPHYR) { mBioParameters.get(heartRatePos).scaledValue = mBioDataProcessor.mZephyrHeartRate/3; mBioParameters.get(heartRatePos).rawValue = mBioDataProcessor.mZephyrHeartRate; } mBioParameters.get(respRatePos).scaledValue = (int) mBioDataProcessor.mRespRate * 5; mBioParameters.get(respRatePos).rawValue = (int) mBioDataProcessor.mRespRate; mBioParameters.get(skinTempPos).scaledValue = (int) mBioDataProcessor.mSkinTempF; mBioParameters.get(skinTempPos).rawValue = (int) mBioDataProcessor.mSkinTempF; } synchronized(mRespRateAverageLock) { mRespRateTotal += mBioDataProcessor.mRespRate; mRespRateIndex++; } // See if we are configured to update display every time we get sensor data if (mDisplaySampleRate == 9999) { this.runOnUiThread(Timer_Tick); } break; } // End case SPINEFunctionConstants.ZEPHYR: case SPINEFunctionConstants.MINDSET: { Node source = data.getNode(); MindsetData mindsetData = (MindsetData) data; mBioDataProcessor.processMindsetData(data, currentMindsetData); if (mindsetData.exeCode == Constants.EXECODE_SPECTRAL || mindsetData.exeCode == Constants.EXECODE_RAW_ACCUM) { numTicsWithoutData = 0; synchronized(mKeysLock) { for (int i = 0; i < MindsetData.NUM_BANDS + 2; i++) { // 2 extra, for attention and meditation mBioParameters.get(i).scaledValue = currentMindsetData.getFeatureValue(i); mBioParameters.get(i).rawValue = currentMindsetData.getFeatureValue(i); } } // See if we are configured to update display every time we get sensor data if (mDisplaySampleRate == 9999) { this.runOnUiThread(Timer_Tick); } } if (mindsetData.exeCode == Constants.EXECODE_POOR_SIG_QUALITY) { // Now show signal strength as bars int sigQuality = mindsetData.poorSignalStrength & 0xff; ImageView image = (ImageView) findViewById(R.id.imageView1); if (sigQuality == 200) image.setImageResource(R.drawable.signal_bars0); else if (sigQuality > 150) image.setImageResource(R.drawable.signal_bars1); else if (sigQuality > 100) image.setImageResource(R.drawable.signal_bars2); else if (sigQuality > 50) image.setImageResource(R.drawable.signal_bars3); else if (sigQuality > 25) image.setImageResource(R.drawable.signal_bars4); else image.setImageResource(R.drawable.signal_bars5); if (sigQuality == 200 && mPrevSigQuality != 200) { Toast.makeText (getApplicationContext(), "Headset not makeing good skin contact. Please Adjust", Toast.LENGTH_LONG).show (); } mPrevSigQuality = sigQuality; } break; } // End case SPINEFunctionConstants.MINDSET: } // End switch (data.getFunctionCode()) } // End if (data != null) } // Note that this is really inaptly named. This simply gets called a certain time period after // discovery is initiated (2 sec?) @Override public void discoveryCompleted(Vector activeNodes) { Log.d(TAG, this.getClass().getSimpleName() + ".discoveryCompleted()"); // Tell the bluetooth service to send us a list of bluetooth devices and system status // Response comes in public void onStatusReceived(BioFeedbackStatus bfs) STATUS_PAIRED_DEVICES mManager.pollBluetoothDevices(); } /** * This callback is called whenever the AndroidBTService sends us an indication that * it is actively trying to establish a BT connection to one of the nodes. * * @see com.t2.SpineReceiver.OnBioFeedbackMessageRecievedListener#onStatusReceived(com.t2.SpineReceiver.BioFeedbackStatus) */ @Override public void onStatusReceived(BioFeedbackStatus bfs) { String name = bfs.name; if (name == null ) name = "sensor node"; if(bfs.messageId.equals("CONN_CONNECTING")) { Log.d(TAG, "Received command : " + bfs.messageId + " to " + name ); Toast.makeText (getApplicationContext(), "Connecting to " + name, Toast.LENGTH_SHORT).show (); } else if(bfs.messageId.equals("CONN_ANY_CONNECTED")) { Log.d(TAG, "Received command : " + bfs.messageId + " to " + name ); // Something has connected - discover what it was mManager.discoveryWsn(); Toast.makeText (getApplicationContext(), name + " Connected", Toast.LENGTH_SHORT).show (); } else if(bfs.messageId.equals("CONN_CONNECTION_LOST")) { Log.d(TAG, "Received command : " + bfs.messageId + " to " + name ); Toast.makeText (getApplicationContext(), name + " Connection lost ****", Toast.LENGTH_SHORT).show (); } else if(bfs.messageId.equals("STATUS_PAIRED_DEVICES")) { Log.d(TAG, "Received command : " + bfs.messageId + " to " + name ); Log.d(TAG, bfs.address ); // We don't want to take any action unless we're ready to go if (!mIsActive) return; populateBioSensors(bfs.address); validateBioSensors(); // Send startup command to every shimmer device for (GraphBioParameter param : mBioParameters) { if (param.isShimmer && param.shimmerNode != null) { ShimmerNonSpineSetupSensor setup = new ShimmerNonSpineSetupSensor(); setup.setSensor(param.shimmerSensorConstant); String deviceAddress = SharedPref.getDeviceForParam(this, param.title1); if (deviceAddress != null) { setup.setBtAddress(Util.AsciiBTAddressToBytes(deviceAddress)); byte startShimmercommand; String s = SharedPref.getString(this, "sensor_sample_rate" ,"4"); int sensorSampleRate = Integer.parseInt(s); Log.d(TAG, "Initializing sensor sample rate to " + sensorSampleRate); switch (sensorSampleRate) { default: case 4: startShimmercommand = ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_RUNNING_4HZ_AUTORANGE; break; case 10: startShimmercommand = ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_RUNNING_10HZ_AUTORANGE; break; case 32: startShimmercommand = ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_RUNNING_32HZ_AUTORANGE; break; case 50: startShimmercommand = ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_RUNNING_50HZ_AUTORANGE; break; case 64: startShimmercommand = ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_RUNNING_64HZ_AUTORANGE; break; case 100: startShimmercommand = ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_RUNNING_100HZ_AUTORANGE; break; case 125: startShimmercommand = ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_RUNNING_125HZ_AUTORANGE; break; } setup.setCommand(startShimmercommand); mConfiguredGSRRange = Util.getGsrRangeFromShimmerCommand(startShimmercommand); Log.d(TAG, String.format("Setting up Shimmer sensor: %s (%s) (%d) SHIMMER_COMMAND_RUNNING", param.shimmerNode.getPhysicalID(), deviceAddress, param.shimmerSensorConstant)); mManager.setup(param.shimmerNode, setup); } else { } } } } } /** * This method is called directly by the timer and runs in the same thread as the timer * From here We call the method that will work with the UI through the runOnUiThread method. */ private void TimerMethod() { this.runOnUiThread(Timer_Tick); } /** * This method runs in the same thread as the UI. */ private Runnable Timer_Tick = new Runnable() { public void run() { numTicsWithoutData++; if (mPaused == true || currentMindsetData == null) { // if (mPaused == true || currentMindsetData == null || numTicsWithoutData > 2) { return; } String bandValuesString = ""; // Output a point for each visible key item int keyCount = mBioParameters.size(); for(int i = 0; i < mBioParameters.size(); ++i) { GraphBioParameter item = mBioParameters.get(i); int rawValue = item.rawValue; int scaledValue = item.scaledValue; if(!item.visible) { continue; } // Special case for GSR since it's actually a float scaled to fit into an int if (gsrPos == i) { float conductance = (float) rawValue / 1000F; bandValuesString += item.title1 + ":" + conductance + ", "; } else { bandValuesString += item.title1 + ":" + rawValue + ", "; } item.series.add(mSpineChartX, scaledValue); if (item.series.getItemCount() > SPINE_CHART_SIZE) { item.series.remove(0); } } mSpineChartX++; if (mDeviceChartView != null) { mDeviceChartView.repaint(); } mTextInfoView.setText(bandValuesString); } }; private void respRateAverageMethod() { this.runOnUiThread(respRate_Average_Tick); } /** * This method runs in the same thread as the UI. */ private Runnable respRate_Average_Tick = new Runnable() { public void run() { if (mRespRateIndex == 0) return; final int rrAvg; synchronized(mRespRateAverageLock) { rrAvg = mRespRateTotal / mRespRateIndex; mRespRateTotal = 0; mRespRateIndex = 0; } // Send data to output DataOutPacket packet = new DataOutPacket(); packet.add(DataOutHandlerTags.AVERAGE_RESP_RATE, rrAvg); try { mDataOutHandler.handleDataOut(packet); } catch (DataOutHandlerException e) { Log.e(TAG, e.toString()); // e.printStackTrace(); } } }; /** * Goes through all all parameters in "keyItems", it saves the visible ones to the long array toggledIds[] * Then calls setVisibleIds to save this long list to a string list in SharedPref at * * "results_visible_ids_measure2" */ private void saveVisibleKeyIds() { ArrayList<Long> toggledIds = new ArrayList<Long>(); for(int i = 0; i < mBioParameters.size(); ++i) { GraphBioParameter item = mBioParameters.get(i); if(item.visible) { toggledIds.add(item.id); } } setVisibleIds(KEY_NAME, toggledIds); } /** * Saves long array of ids to a string array at * "results_visible_ids_measure3" * * @param keySuffix Id of the array * @return a long array containing ids of parameters that are visible */ private void setVisibleIds(String keyName, ArrayList<Long> ids) { SharedPref.setValues( sharedPref, keyName, ",", ArraysExtra.toStringArray(ids.toArray(new Long[ids.size()])) ); } /** * * @param keySuffix id of the array * @param ids long array of ids to save to Shared Params */ private ArrayList<Long> getVisibleIds(String keyName) { String[] idsStrArr = SharedPref.getValues( sharedPref, keyName, ",", new String[0] ); return new ArrayList<Long>( Arrays.asList( ArraysExtra.toLongArray(idsStrArr) ) ); } /** * Returns a unique key color based on the current index and total count of parameters * @param currentIndex Index of current parameter * @param totalCount Total number of parameters * @return Unique color based in inputs */ protected int getKeyColor(int currentIndex, int totalCount) { float hue = currentIndex / (1.00f * totalCount) * 360.00f; return Color.HSVToColor( 255, new float[]{ hue, 1.0f, 1.0f } ); } public void onButtonClick(View v) { final int id = v.getId(); switch (id) { // case R.id.buttonBack: // finish(); // // break; // case R.id.buttonAddMeasure: boolean toggleArray[] = new boolean[mBioParameters.size()]; for(int j = 0; j < mBioParameters.size(); ++j) { GraphBioParameter item = mBioParameters.get(j); if(item.visible) toggleArray[j] = true; else toggleArray[j] = false; } String[] measureNames = new String[mBioParameters.size()]; int i = 0; for (GraphBioParameter item: mBioParameters) { measureNames[i++] = item.title1; } // Present dialog to allow user to choose which parameters to view in this activity AlertDialog.Builder alert = new AlertDialog.Builder(this); alert.setTitle(R.string.alert_dialog_measure_selector); // alert.setMultiChoiceItems(R.array.measure_select_dialog_items, alert.setMultiChoiceItems(measureNames, toggleArray, new DialogInterface.OnMultiChoiceClickListener() { public void onClick(DialogInterface dialog, int whichButton,boolean isChecked) { GraphBioParameter item = mBioParameters.get(whichButton); item.visible = item.visible ? false: true; saveVisibleKeyIds(); generateChart(); } }); alert.setPositiveButton(R.string.alert_dialog_ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { generateChart(); } }); alert.show(); break; case R.id.buttonPause: if (mPaused == true) { mPaused = false; mPauseButton.getBackground().setColorFilter(Color.LTGRAY, PorterDuff.Mode.MULTIPLY); mPauseButton.setText(R.string.button_running); if (mLoggingEnabled) { try { mDataOutHandler.logNote(getString(R.string.un_paused)); } catch (DataOutHandlerException e) { Log.e(TAG, e.toString()); e.printStackTrace(); } // data header } if (mLogCatEnabled) { Log.d(TAG, "Un-Paused" ); } } else { mPaused = true; mPauseButton.getBackground().setColorFilter(0xFFFF0000, PorterDuff.Mode.MULTIPLY); mPauseButton.setText(R.string.button_pause); if (mLoggingEnabled) { try { mDataOutHandler.logNote(getString(R.string.paused)); } catch (DataOutHandlerException e) { Log.e(TAG, e.toString()); e.printStackTrace(); } // data header } if (mLogCatEnabled) { Log.d(TAG, "Paused" ); } } break; } // End switch } /** * Sets up all parameters for display of both the chart on the screen * AND a color coded display of the parameters and their values */ private void generateChart() { // Set up chart XYMultipleSeriesDataset deviceDataset = new XYMultipleSeriesDataset(); XYMultipleSeriesRenderer deviceRenderer = new XYMultipleSeriesRenderer(); LinearLayout layout = (LinearLayout) findViewById(R.id.deviceChart); if (mDeviceChartView != null) { layout.removeView(mDeviceChartView); } if (true) { mDeviceChartView = ChartFactory.getLineChartView(this, deviceDataset, deviceRenderer); mDeviceChartView.setBackgroundColor(Color.BLACK); layout.addView(mDeviceChartView, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); } deviceRenderer.setShowLabels(false); deviceRenderer.setMargins(new int[] {0,5,5,0}); deviceRenderer.setShowAxes(true); deviceRenderer.setShowLegend(false); deviceRenderer.setZoomEnabled(false, false); deviceRenderer.setPanEnabled(false, false); deviceRenderer.setYAxisMin(0); deviceRenderer.setYAxisMax(100); SpannableStringBuilder sMeasuresText = new SpannableStringBuilder("Displaying: "); ArrayList<Long> visibleIds = getVisibleIds(KEY_NAME); int keyCount = mBioParameters.size(); keyCount = mBioParameters.size(); int lineNum = 0; for(int i = 0; i < mBioParameters.size(); ++i) { GraphBioParameter item = mBioParameters.get(i); item.visible = visibleIds.contains(item.id); if(!item.visible) { continue; } deviceDataset.addSeries(item.series); item.color = getKeyColor(i, keyCount); // Add name of the measure to the displayed text field ForegroundColorSpan fcs = new ForegroundColorSpan(item.color); int start = sMeasuresText.length(); sMeasuresText.append(mBioParameters.get(i).title1 + ", "); int end = sMeasuresText.length(); sMeasuresText.setSpan(fcs, start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE); if (sMeasuresText.length() > 40 && lineNum == 0) { lineNum++; } XYSeriesRenderer seriesRenderer = new XYSeriesRenderer(); seriesRenderer.setColor(item.color); seriesRenderer.setPointStyle(PointStyle.CIRCLE); deviceRenderer.addSeriesRenderer(seriesRenderer); } mMeasuresDisplayText.setText(sMeasuresText) ; } /** * Receives a json string containing data about all of the paired sensors * the adds a new BioSensor for each one to the mBioSensors collection * * @param jsonString String containing info on all paired devices */ private void populateBioSensors(String jsonString) { Log.d(TAG, this.getClass().getSimpleName() + " populateBioSensors"); // Now clear it out and populate it. The only difference is that // if a sensor previously existed, then mBioSensors.clear(); try { JSONArray jsonArray = new JSONArray(jsonString); for (int i = 0; i < jsonArray.length(); i++) { JSONObject jsonObject = jsonArray.getJSONObject(i); Boolean enabled = jsonObject.getBoolean("enabled"); String name = jsonObject.getString("name"); String address = jsonObject.getString("address"); int connectionStatus = jsonObject.getInt("connectionStatus"); if (name.equalsIgnoreCase("system")) { mBluetoothEnabled = enabled; } else { Log.d(TAG, "Adding sensor " + name + ", " + address + (enabled ? ", enabled":", disabled") + " : " + Util.connectionStatusToString(connectionStatus)); BioSensor bioSensor = new BioSensor(name, address, enabled); bioSensor.mConnectionStatus = connectionStatus; mBioSensors.add(bioSensor); } } } catch (JSONException e) { Log.e(TAG, e.toString()); } } /** * Validates sensors, makes sure that bluetooth is on and each sensor has a parameter associated with it */ void validateBioSensors() { // First make sure that bluetooth is enabled if (!mBluetoothEnabled) { AlertDialog.Builder alert1 = new AlertDialog.Builder(this); alert1.setMessage("Bluetooth is not enabled on your device. Press OK to go to the wireless system" + "settings to turn it on"); alert1.setPositiveButton("Ok", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { mInstance.startActivityForResult(new Intent(android.provider.Settings.ACTION_BLUETOOTH_SETTINGS), BLUETOOTH_SETTINGS_ID); } }); alert1.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { } }); alert1.show(); } String badSensorName = null; // Now make sure that every device has a parameter associated with it for (BioSensor sensor: mBioSensors) { if (sensor.mEnabled) { String param = SharedPref.getParamForDevice(mInstance, sensor.mBTAddress); //Log.d(TAG, "sensor: " + sensor.mBTName + ", parameter: " + param); if (param == null) { badSensorName = sensor.mBTName; break; } } } // end for (BioSensor sensor: mBioSensors) if (badSensorName != null) { AlertDialog.Builder alert1 = new AlertDialog.Builder(this); alert1.setMessage("Sensor " + badSensorName + " is enabled but " + " does not have a parameter associated with it." + "Press Ok to associate one"); alert1.setPositiveButton("Ok", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { Intent intent2 = new Intent(mInstance, DeviceManagerActivity.class); mInstance.startActivity(intent2); } }); alert1.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { } }); alert1.show(); } } /** * Wrapper for BioParameter that has a graph element * * @author scott.coleman * */ static class GraphBioParameter extends BioParameter{ public XYSeries series; public Boolean isShimmer; Node shimmerNode; byte shimmerSensorConstant; public GraphBioParameter(long id, String title1, String title2, Boolean enabled) { super(id, title1, title2, enabled); isShimmer = false; shimmerNode = null; shimmerSensorConstant = 0; series = new XYSeries(title1); } } // ---------------------------------------------------------- // ANT specific stuff //----------------------------------------------------------- @Override public void errorCallback() { Log.e(TAG, "ANT error"); } @Override public void notifyAntStateChanged() { // TODO Auto-generated method stub } @Override public void notifyChannelStateChanged(byte channel) { // TODO Auto-generated method stub } @Override public void notifyChannelDataChanged(byte channel) { // Log.i(TAG, "notifyChannelDataChanged"); HeartBeatData thisData = new HeartBeatData(); thisData.setFunctionCode(SPINEFunctionConstants.HEARTBEAT); thisData.setBPM(mAntManager.getBPM()); this.received(thisData); } private final ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { //This is very unlikely to happen with a local service (ie. one in the same process) mAntManager.setCallbacks(null); mAntManager = null; } @Override public void onServiceConnected(ComponentName name, IBinder service) { mAntManager = ((ANTPlusService.LocalBinder)service).getManager(); mAntManager.setCallbacks(Graphs1Activity.this); loadAntState(); notifyAntStateChanged(); // Start ANT automatically mAntManager.doEnable(); Log.i(TAG, "Starting heart rate data"); mAntManager.openChannel(AntPlusManager.HRM_CHANNEL, true); mAntManager.requestReset(); } }; /** * Store application persistent data. */ private void saveAntState() { // Save current Channel Id in preferences // We need an Editor object to make changes SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0); SharedPreferences.Editor editor = settings.edit(); editor.putInt("DeviceNumberHRM", mAntManager.getDeviceNumberHRM()); editor.putInt("DeviceNumberSDM", mAntManager.getDeviceNumberSDM()); editor.putInt("DeviceNumberWGT", mAntManager.getDeviceNumberWGT()); editor.putInt("ProximityThreshold", mAntManager.getProximityThreshold()); editor.putInt("BufferThreshold", mAntManager.getBufferThreshold()); editor.commit(); } /** * Retrieve application persistent data. */ private void loadAntState() { // Restore preferences SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0); mAntManager.setDeviceNumberHRM((short) settings.getInt("DeviceNumberHRM", ANT_WILDCARD)); mAntManager.setDeviceNumberSDM((short) settings.getInt("DeviceNumberSDM", ANT_WILDCARD)); mAntManager.setDeviceNumberWGT((short) settings.getInt("DeviceNumberWGT", ANT_WILDCARD)); mAntManager.setProximityThreshold((byte) settings.getInt("ProximityThreshold", ANT_DEFAULT_BIN)); mAntManager.setBufferThreshold((short) settings.getInt("BufferThreshold", ANT_DEFAULT_BUFFER_THRESHOLD)); } private long map(long x, long in_min, long in_max, long out_min, long out_max) { return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } private double map(double x, double in_min, double in_max, double out_min, double out_max) { return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } }